#include "SLAE.h"

SLAE::SLAE(cl_uint sizeVector, float precision)
{
	size = sizeVector;
	solvePrecision = precision;
	initializeHost();
	initializeCL();
}

SLAE::~SLAE()
{
	cleanupCL();
	cleanupHost();
}

void SLAE::initializeHost()
{
	pMatrix			= NULL;
	pVector			= NULL;
	pResult			= NULL;
	pNextSolution	= NULL;

	pMatrix = (cl_float*) malloc (size * size * sizeof(cl_float));
	if(pMatrix == NULL)
	{
		cout << "Error: Failed to allocate pMatrix memory on host" << endl;
		return;
	}

	pVector = (cl_float*) malloc (size * sizeof(cl_float));
	if(pVector == NULL)
	{
		cout << "Error: Failed to allocate pVector memory on host" << endl;
		return;
	}

	pResult = (cl_float*) malloc (size * sizeof(cl_float));
	if(pResult == NULL)
	{
		cout << "Error: Failed to allocate pResult memory on host" << endl;
		return;
	}

	pNextSolution = (cl_float*) malloc (size * sizeof(cl_float));
	if(pNextSolution == NULL)
	{
		cout << "Error: Failed to allocate pNextSolution memory on host" << endl;
		return;
	}
	// Initial values
	unsigned int i=0, j=0;
	for(i=0; i<size; i++)
	{
		pVector[i] = i+1;
		pResult[i] = 0;
		pNextSolution[i] = -1;
		for(j=0; j<size; j++)
		{
			if(j <= i)
				pMatrix[i*size + j] = 1;
			else
				pMatrix[i*size + j] = 0;
			
		}
	}


}

// OpenCL related initializations are done here.
// Context, Device list, Command Queue are set up.
// Calls are made to set up OpenCL memory buffers that this program uses
// and to load the programs into memory and get kernel handles.
void SLAE::initializeCL(void)
{
	cl_int status = 0;
    size_t deviceListSize;

    // Have a look at the available platforms and pick either
    // the AMD one if available or a reasonable default.

    cl_uint numPlatforms;
    cl_platform_id platform = NULL;
	// obtain the list of platforms available.
    status = clGetPlatformIDs(0, NULL, &numPlatforms);
    if(status != CL_SUCCESS)
    {
        printf("Error: Getting Platforms.\n");
		printError(status, "clGetPlatformsIDs");
        return;
    }
    
    if(numPlatforms > 0)
    {
        cl_platform_id* platforms = (cl_platform_id *)malloc(numPlatforms*sizeof(cl_platform_id));
        status = clGetPlatformIDs(numPlatforms, platforms, NULL);
        if(status != CL_SUCCESS)
        {
            printf("Error: Getting Platform Ids.\n");
			printError(status, "clGetPlatformsIDs");
            return;
        }
        for(unsigned int i=0; i < numPlatforms; ++i)
        {
            char pbuff[100];
			// get specific information about the OpenCL platform
            status = clGetPlatformInfo(
                        platforms[i],
                        CL_PLATFORM_VENDOR,
                        sizeof(pbuff),
                        pbuff,
                        NULL);
            if(status != CL_SUCCESS)
            {
                printf("Error: Getting Platform Info.\n");
				printError(status, "clGetPlatformInfo");
                return;
            }
            platform = platforms[i];
            if(!strcmp(pbuff, "Advanced Micro Devices, Inc."))
            {
                break;
            }
        }
        delete platforms;
    }

    if(NULL == platform)
    {
        cout << "NULL platform found so Exiting Application." << endl;
        return;
    }

    
    //If we could find our platform, use it. Otherwise use just available platform.
    
    cl_context_properties cps[3] = { CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0 };


	
	// Create an OpenCL context
	context = clCreateContextFromType(cps, 
                                      CL_DEVICE_TYPE_GPU,
                                      NULL, 
                                      NULL, 
                                      &status);
    if(status != CL_SUCCESS) 
	{  
		printf("Error: Creating Context.\n");
		printError(status, "clCreateContextFromType");
		return; 
	}

    // First, get the size of device list data 
    status = clGetContextInfo(context, 
                              CL_CONTEXT_DEVICES, 
                              0, 
                              NULL, 
                              &deviceListSize);
    if(status != CL_SUCCESS) 
	{  
		printf(
			"Error: Getting Context Info \
		    (device list size, clGetContextInfo)\n");
		printError(status, "clGetContextInfo");
		return;
	}

	// Detect OpenCL devices
    devices = (cl_device_id *)malloc(deviceListSize);
	if(devices == 0)
	{
		printf("Error: No devices found.\n");
		return;
	}

    // Now, get the device list data 
    status = clGetContextInfo(
			     context, 
                 CL_CONTEXT_DEVICES, 
                 deviceListSize, 
                 devices, 
                 NULL);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: Getting Context Info \
		    (device list, clGetContextInfo)\n");
		printError(status, "clGetContextInfo");
		return;
	}

	// Create an OpenCL command queue
    commandQueue = clCreateCommandQueue(
					   context, 
                       devices[0], 
                       0, 
                       &status);
    if(status != CL_SUCCESS) 
	{ 
		printf("Creating Command Queue. (clCreateCommandQueue)\n");
		printError(status, "clCreateCommandQueue");
		return;
	}

	// Create OpenCL memory buffers
	pMatrixBuffer = clCreateBuffer(
						context, 
						CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
						sizeof(cl_float) * size * size,
						pMatrix, 
						&status);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: clCreateBuffer\n");
		printError(status, "clCreateBuffer");
		return;
	}
	// Create OpenCL memory buffers
    pVectorBuffer = clCreateBuffer(
					   context, 
                       CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                       sizeof(cl_float) * size,
                       pVector, 
                       &status);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: clCreateBuffer\n");
		printError(status, "clCreateBuffer");
		return;
	}
	// Create OpenCL memory buffers
    pResultBuffer = clCreateBuffer(
					   context, 
                       CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                       sizeof(cl_float) * size,
                       pResult, 
                       &status);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: clCreateBuffer\n");
		printError(status, "clCreateBuffer");
		return;
	}

	// Create OpenCL memory buffers
    pNextSolutionBuffer = clCreateBuffer(
					   context, 
                       CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                       sizeof(cl_float) * size,
                       pNextSolution,
                       &status);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: clCreateBuffer\n");
		printError(status, "clCreateBuffer");
		return;
	}
	
	// Load CL file, build CL program object, create CL kernel object
    const char * filename  = "SLAE.cl";
    string  sourceStr = convertToString(filename);
    const char * source    = sourceStr.c_str();
    size_t sourceSize[]    = { strlen(source) };

    program = clCreateProgramWithSource(
			      context, 
                  1, 
                  &source,
				  sourceSize,
                  &status);
	if(status != CL_SUCCESS) 
	{ 
	    printf("Error: Loading Binary into cl_program.\n");
		printError(status, "clCreateProgramWithBinary");
	  return;
	}

    // create a cl program executable for all the devices specified 
    status = clBuildProgram(program, 1, devices, NULL, NULL, NULL);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: Building Program\n");
		printError(status, "clBuildProgram");
		return; 
	}

    // get a kernel object handle for a kernel with the given name 
    kernel = clCreateKernel(program, "Main", &status);
    if(status != CL_SUCCESS) 
	{  
		printf("Error: Creating Kernel from program.\n");
		printError(status, "clCreateKernel");
		return;
	}
}

// convert code from file.cl to string
string SLAE::convertToString(const char * filename)
{
	size_t size;
	char * str;
	string code;

	fstream file(filename, (fstream::in | fstream::binary));

	if(file.is_open())
	{
		size_t fileSize;
		file.seekg(0, fstream::end);
		size=fileSize=file.tellg();
		file.seekg(0, fstream::beg);

		str = new char[size+1];
		if(!str)
		{
			file.close();
			return NULL;
		}

		file.read(str, fileSize);
		file.close();
		str[size] = '\0';

		code = str;

		return code;
	}
	return NULL;
}
// this is called once the OpenCL context, memory etc. are set up,
// the program is loaded into memory and the kernel handles are ready.
// It sets the values for kernels' arguments and enqueues calls to the kernels
// on to the command queue and waits till the calls have finished execution.
// It also gets kernel start and end time if profiling is enabled.
void SLAE::runCLKernels(void)
{
	cl_int   status;
    cl_event events[2];
    size_t globalThreads[1];
    size_t localThreads[1];

    
    globalThreads[0] = size;
	localThreads[0] = 1;

    // === Set appropriate arguments to the kernel ===
	// the matrix to the kernel (input)
	status = clSetKernelArg(
					kernel, 
					0,				
					sizeof(cl_mem), 
					(void *)&pMatrixBuffer);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: pMatrixBuffer");
		return;
	}
    // the  vector to the kernel (input)
    status = clSetKernelArg(
                    kernel, 
                    1,
                    sizeof(cl_mem), 
                    (void *)&pVectorBuffer);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: pVectorBuffer");
		return;
	}

	// the result to the kernel (output)
    status = clSetKernelArg(
                    kernel, 
                    2,
                    sizeof(cl_mem), 
                    (void *)&pResultBuffer);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: pResultBuffer");
		return;
	}

	// size of vector to the kernel (input)
	status = clSetKernelArg(
					kernel, 
					3,
					sizeof(cl_uint), 
					(void *)&size);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: size");
		return;
	}

	// solve precision to the kernel (input)
	status = clSetKernelArg(
					kernel, 
					4,
					sizeof(cl_float), 
					(void *)&solvePrecision);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: size");
		return;
	}

	// the matrix to the kernel (input)
	status = clSetKernelArg(
					kernel, 
					5,				
					sizeof(cl_mem), 
					(void *)&pNextSolutionBuffer);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: pMatrixBuffer");
		return;
	}

	startGPU = omp_get_wtime();	// get start time

	// Enqueue a kernel run call.
    status = clEnqueueNDRangeKernel(
			     commandQueue,
                 kernel,
                 1,
                 NULL,
                 globalThreads,
                 localThreads,
                 0,
                 NULL,
                 &events[0]);

	endGPU = omp_get_wtime();	// get end time

    if(status != CL_SUCCESS) 
	{ 
		printf(
			"Error: Enqueueing kernel onto command queue.\n");
		printError(status, "clEnqueueNDRangeKernel");
		return;
	}

	clFinish(commandQueue);

    // wait for the kernel call to finish execution
    status = clWaitForEvents(1, &events[0]);
    if(status != CL_SUCCESS) 
	{ 
		printf(
		    "Error: Waiting for kernel run to finish.\n");
		printError(status, "clWaitForEvents");
		return;
	}

    status = clReleaseEvent(events[0]);
    if(status != CL_SUCCESS)
    {
        printf("Error: clReleaseEvent.\n");
		printError(status, "events[0]");
        return;
    }

    // Enqueue readBuffer
    status = clEnqueueReadBuffer(
                commandQueue,
                pResultBuffer,
                CL_TRUE,
                0,
                size * sizeof(cl_float),
                pResult,
                0,
                NULL,
                &events[1]);
    
    if(status != CL_SUCCESS) 
	{
        printf("Error: clEnqueueReadBuffer failed.\n");
		printError(status, "clEnqueueReadBuffer");
    }

	// Wait for the read buffer to finish execution
    status = clWaitForEvents(1, &events[1]);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: Waiting for read buffer call to finish.\n");
		printError(status, "clWaitForEvents");
		return;
	}
    
    status = clReleaseEvent(events[1]);
    if(status != CL_SUCCESS)
    {
        printf("Error: clReleaseEvent.\n");
		printError(status, "events[1]");
        return;
    }
}
void SLAE::cleanupHost(void)
{
	if(pMatrix != NULL)
	{
		free(pMatrix);
		pMatrix = NULL;
	}

	if(pVector != NULL)
	{
		free(pVector);
		pVector = NULL;
	}
	
	if(pResult != NULL)
	{
		free(pResult);
		pResult = NULL;
	}
}
void SLAE::cleanupCL(void)
{
	cl_int status;

    status = clReleaseKernel(kernel);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseKernel \n");
		return; 
	}
    status = clReleaseProgram(program);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseProgram\n");
		return; 
	}
    
	status = clReleaseMemObject(pMatrixBuffer);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseMemObject (pMatrixBuffer)\n");
		return; 
	}

	status = clReleaseMemObject(pVectorBuffer);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseMemObject (pVectorBuffer)\n");
		return; 
	}

	status = clReleaseMemObject(pResultBuffer);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseMemObject (pResultBuffer)\n");
		return; 
	}

	status = clReleaseCommandQueue(commandQueue);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseCommandQueue\n");
		return;
	}
    status = clReleaseContext(context);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseContext\n");
		return;
	}
}

void SLAE::printError(cl_int status, char * nameFunction)
{
	// print name of error
	cout << "Name of function: " << nameFunction << "; Errors code: ";

	switch(status)
	{
	case CL_DEVICE_NOT_FOUND:
		cout << "CL_DEVICE_NOT_FOUND" << endl;
		break;
	case CL_DEVICE_NOT_AVAILABLE:
		cout << "CL_DEVICE_NOT_AVAILABLE" << endl;
		break;
	case CL_COMPILER_NOT_AVAILABLE:
		cout << "CL_COMPILER_NOT_AVAILABLE" << endl;
		break;
	case CL_MEM_OBJECT_ALLOCATION_FAILURE:
		cout << "CL_MEM_OBJECT_ALLOCATION_FAILURE" << endl;
		break;
	case CL_OUT_OF_RESOURCES:
		cout << "CL_OUT_OF_RESOURCES" << endl;
		break;
	case CL_OUT_OF_HOST_MEMORY:
		cout << "CL_OUT_OF_HOST_MEMORY" << endl;
		break;
	case CL_PROFILING_INFO_NOT_AVAILABLE:
		cout << "CL_PROFILING_INFO_NOT_AVAILABLE" << endl;
		break;
	case CL_MEM_COPY_OVERLAP:
		cout << "CL_MEM_COPY_OVERLAP" << endl;
		break;
	case CL_IMAGE_FORMAT_MISMATCH:
		cout << "CL_IMAGE_FORMAT_MISMATCH" << endl;
		break;
	case CL_IMAGE_FORMAT_NOT_SUPPORTED:
		cout << "CL_IMAGE_FORMAT_NOT_SUPPORTED" << endl;
		break;
	case CL_BUILD_PROGRAM_FAILURE:
		cout << "CL_BUILD_PROGRAM_FAILURE" << endl;
		break;
	case CL_MAP_FAILURE:
		cout << "CL_MAP_FAILURE" << endl;
		break;
	
	case CL_INVALID_VALUE:
		cout << "CL_INVALID_VALUE" << endl;
		break;
	case CL_INVALID_DEVICE_TYPE:
		cout << "CL_INVALID_DEVICE_TYPE" << endl;
		break;
	case CL_INVALID_PLATFORM:
		cout << "CL_INVALID_PLATFORM" << endl;
		break;
	case CL_INVALID_DEVICE:
		cout << "CL_INVALID_DEVICE" << endl;
		break;
	case CL_INVALID_CONTEXT:
		cout << "CL_INVALID_CONTEXT" << endl;
		break;
	case CL_INVALID_QUEUE_PROPERTIES:
		cout << "CL_INVALID_QUEUE_PROPERTIES" << endl;
		break;
	case CL_INVALID_COMMAND_QUEUE:
		cout << "CL_INVALID_COMMAND_QUEUE" << endl;
		break;
	case CL_INVALID_HOST_PTR:
		cout << "CL_INVALID_HOST_PTR" << endl;
		break;
	case CL_INVALID_MEM_OBJECT:
		cout << "CL_INVALID_MEM_OBJECT" << endl;
		break;
	case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR:
		cout << "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR" << endl;
		break;
	case CL_INVALID_IMAGE_SIZE:
		cout << "CL_INVALID_IMAGE_SIZE" << endl;
		break;
	case CL_INVALID_SAMPLER:
		cout << "CL_INVALID_SAMPLER" << endl;
		break;
	case CL_INVALID_BINARY:
		cout << "CL_INVALID_BINARY" << endl;
		break;
	case CL_INVALID_BUILD_OPTIONS:
		cout << "CL_INVALID_BUILD_OPTIONS" << endl;
		break;
	case CL_INVALID_PROGRAM:
		cout << "CL_INVALID_PROGRAM" << endl;
		break;
	case CL_INVALID_PROGRAM_EXECUTABLE:
		cout << "CL_INVALID_PROGRAM_EXECUTABLE" << endl;
		break;
	case CL_INVALID_KERNEL_NAME:
		cout << "CL_INVALID_KERNEL_NAME" << endl;
		break;
	case CL_INVALID_KERNEL_DEFINITION:
		cout << "CL_INVALID_KERNEL_DEFINITION" << endl;
		break;
	case CL_INVALID_KERNEL:
		cout << "CL_INVALID_KERNEL" << endl;
		break;
	case CL_INVALID_ARG_INDEX:
		cout << "CL_INVALID_ARG_INDEX" << endl;
		break;
	case CL_INVALID_ARG_VALUE:
		cout << "CL_INVALID_ARG_VALUE" << endl;
		break;
	case CL_INVALID_ARG_SIZE:
		cout << "CL_INVALID_ARG_SIZE" << endl;
		break;
	case CL_INVALID_KERNEL_ARGS:
		cout << "CL_INVALID_KERNEL_ARGS" << endl;
		break;
	case CL_INVALID_WORK_DIMENSION:
		cout << "CL_INVALID_WORK_DIMENSION" << endl;
		break;
	case CL_INVALID_WORK_GROUP_SIZE:
		cout << "CL_INVALID_WORK_GROUP_SIZE" << endl;
		break;
	case CL_INVALID_WORK_ITEM_SIZE:
		cout << "CL_INVALID_WORK_ITEM_SIZE" << endl;
		break;
	case CL_INVALID_GLOBAL_OFFSET:
		cout << "CL_INVALID_GLOBAL_OFFSET" << endl;
		break;
	case CL_INVALID_EVENT_WAIT_LIST:
		cout << "CL_INVALID_EVENT_WAIT_LIST" << endl;
		break;
	case CL_INVALID_EVENT:
		cout << "CL_INVALID_EVENT" << endl;
		break;
	case CL_INVALID_OPERATION:
		cout << "CL_INVALID_OPERATION" << endl;
		break;
	case CL_INVALID_GL_OBJECT:
		cout << "CL_INVALID_GL_OBJECT" << endl;
		break;
	case CL_INVALID_BUFFER_SIZE:
		cout << "CL_INVALID_BUFFER_SIZE" << endl;
		break;
	case CL_INVALID_MIP_LEVEL:
		cout << "CL_INVALID_MIP_LEVEL" << endl;
		break;
	case CL_INVALID_GLOBAL_WORK_SIZE:
		cout << "CL_INVALID_GLOBAL_WORK_SIZE" << endl;
		break;
	default:
		break;
	}

	// print log information
	char programLog[1024];
	status = clGetProgramBuildInfo(program, devices[0], CL_PROGRAM_BUILD_LOG, 1024, programLog, 0);
	cout << programLog << endl;
}



void SLAE::printTest()
{
	/*
	cout << "Matrix: " << endl;
	for(unsigned int i=0; i<size; i++)
	{
		for(unsigned int j=0; j<size; j++)
		{
			cout << pMatrix[i*size + j] << " ";
		}
		cout << endl;
	}

	cout << "Vector" << endl;
	for(unsigned int i=0; i<size; i++)
	{
		cout << pVector[i] << " ";
	}
	cout << endl;
	*/
	cout << "Result" << endl;
	for(unsigned int i=0; i<size; i++)
	{
		cout << pResult[i] << " ";
	}
	cout << endl;
}

// size    - dimension of the matrix (size*size)
// pMatrix - coefficient matrix					
// pVector - column of free terms				
// pX	   - first approximation				
void SLAE::Jacobi()
{
	int iter = 0;
	long float * pTempX = new long float[size];
	long float * pX = new long float[size];
	for(unsigned int i=0; i<size; i++)
	{
		pX[i] = 0;
	}

	long float norm = 0.0f;		// norm, defined as the greatest difference between the components of the column 'X' adjacent iterations.
	float eps = solvePrecision;;	//prescribed accuracy
	startCPU = omp_get_wtime();
	do
	{
#pragma omp parallel for
			for (int i = 0; i < size; i++) 
			{
				pTempX[i] = - pVector[i];
	#pragma omp parallel for
				for (int g = 0; g < size; g++) 
				{
					if (i != g)
						pTempX[i] += pMatrix[i*size + g] * pX[g];
				}
				pTempX[i] /= - pMatrix[i*size + i];
			}
        norm = fabs(pX[0] - pTempX[0]);

	#pragma omp parallel for
		for (int h = 0; h < size; h++) 
		{
			if (fabs(pX[h] - pTempX[h]) > norm)
				norm = fabs(pX[h] - pTempX[h]);
			pX[h] = pTempX[h];
		}

		iter++;
	}
	while(norm > eps);
	endCPU = omp_get_wtime();
	cout << iter << endl;
	//cout << "End Jacobi method" << endl;

	cout << "CPU result" << endl;
	for(unsigned int i=0; i<size; i++)
	{
		cout << pX[i] << " ";
	}
	cout << endl;
	delete [] pTempX;
	delete [] pX;
}